import os
import re
import time
import cv2
import numpy as np
import tkinter as tk
from tkinter import ttk
from tkinter import filedialog, messagebox
import threading
import subprocess
import logging
import natsort
from collections import defaultdict
from typing import List, Tuple, Optional, Dict, Any, Callable
from pathlib import Path
from dataclasses import dataclass

# 导入第三方库（放在try-except中）
try:
    from skimage.metrics import structural_similarity as compare_ssim
    from PIL import Image, ImageDraw, ImageFont
    SKIMAGE_AVAILABLE = True
except ImportError:
    SKIMAGE_AVAILABLE = False
    print("警告: 缺少skimage或PIL库，部分功能可能无法使用")

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler("video_processor.log", encoding='utf-8'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

# 配置参数类
@dataclass
class Config:
    """配置参数类"""
    frist_y: int = 483
    second_y: int = 502
    frist_x: int = 140
    second_x: int = 250
    timespan: int = 60
    dissimilarity: float = 0.962
    granularity: int = 60
    frist_frame: int = 30
    filepath: str = r'F:\11'
    image_file: str = '_3630.jpg'
    count: int = 12

    @classmethod
    def from_file(cls, file_path='config.txt'):
        """从配置文件加载配置"""
        config_dict = {}
        
        if not os.path.exists(file_path):
            logger.warning(f"配置文件 {file_path} 不存在，使用默认配置")
            return cls()
        
        # 尝试多种编码读取配置文件
        encodings = ['utf-8', 'gbk', 'gb18030', 'latin1']
        
        for encoding in encodings:
            try:
                with open(file_path, 'r', encoding=encoding) as f:
                    for line in f:
                        line = line.strip()
                        if not line or line.startswith('#'):
                            continue
                        
                        if '=' in line:
                            key, value = line.split('=', 1)
                            key = key.strip()
                            value = value.strip()
                            
                            # 类型转换
                            try:
                                if '.' in value:
                                    config_dict[key] = float(value)
                                else:
                                    config_dict[key] = int(value)
                            except ValueError:
                                config_dict[key] = value
                
                logger.info(f"成功使用 {encoding} 编码读取配置文件")
                return cls(**config_dict)
            
            except UnicodeDecodeError:
                continue
            except Exception as e:
                logger.error(f"读取配置文件时出错: {e}")
                continue
        
        logger.error("无法解码配置文件，使用默认配置")
        return cls()

    def save_to_file(self, file_path='config.txt'):
        """保存配置到文件，保持原有注释和格式"""
        try:
            # 读取现有配置文件的所有行
            existing_lines = []
            if os.path.exists(file_path):
                try:
                    with open(file_path, 'r', encoding='utf-8') as f:
                        existing_lines = f.readlines()
                except Exception as e:
                    logger.warning(f"读取现有配置文件失败: {e}")
                    existing_lines = []

            # 解析现有配置
            config_dict = {}
            other_lines = []  # 保存注释和空行
            
            for line in existing_lines:
                stripped_line = line.strip()
                if not stripped_line or stripped_line.startswith('#'):
                    # 注释或空行
                    other_lines.append(line)
                elif '=' in stripped_line:
                    # 配置项
                    key, value = stripped_line.split('=', 1)
                    key = key.strip()
                    config_dict[key] = (value.strip(), line)  # 保存值和原始行
                else:
                    # 其他内容
                    other_lines.append(line)

            # 更新或添加配置项
            updated_config = {}
            for field in self.__dataclass_fields__:
                value = getattr(self, field)
                updated_config[field] = str(value)

            # 合并配置
            merged_config = {}
            for key, (value, original_line) in config_dict.items():
                if key in updated_config:
                    # 更新现有配置的值
                    merged_config[key] = f"{key}={updated_config[key]}\n"
                else:
                    # 保留不在dataclass中的配置
                    merged_config[key] = original_line

            # 添加新的配置项
            for key, value in updated_config.items():
                if key not in merged_config:
                    merged_config[key] = f"{key}={value}\n"

            # 写入文件
            with open(file_path, 'w', encoding='utf-8') as f:
                # 首先写入注释和空行
                for line in other_lines:
                    f.write(line)
                
                # 写入配置项（按字母顺序排序，便于阅读）
                for key in sorted(merged_config.keys()):
                    f.write(merged_config[key])
                
                # 添加文件说明
                f.write(f"\n# 配置文件自动更新于 {time.strftime('%Y-%m-%d %H:%M:%S')}\n")

            logger.info(f"配置已保存到 {file_path}")
            return True
            
        except Exception as e:
            logger.error(f"保存配置失败: {e}")
            return False


class VideoProcessor:
    def __init__(self):
        self.flag_stop = False
        self.date_first = True
        self.current_file = ""
        self.fps = 30
        self.config = Config.from_file()
        
        # 加载中文字体
        self.font_small, self.font_medium, self.font_large = self._load_fonts()
    
    def _load_fonts(self):
        """加载中文字体"""
        font_paths = [
            "C:/Windows/Fonts/simhei.ttf",  # Windows
            "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf",  # Linux
            "/System/Library/Fonts/PingFang.ttc"  # macOS
        ]
        
        for font_path in font_paths:
            if os.path.exists(font_path):
                try:
                    font_small = ImageFont.truetype(font_path, 24)
                    font_medium = ImageFont.truetype(font_path, 30)
                    font_large = ImageFont.truetype(font_path, 36)
                    logger.info(f"成功加载字体: {font_path}")
                    return font_small, font_medium, font_large
                except Exception as e:
                    logger.warning(f"无法加载字体 {font_path}: {e}")
        
        logger.warning("无法加载中文字体，将使用英文显示")
        return None, None, None

    def frames_to_timecode(self, framerate: float, frames: int) -> str:
        """将帧数转换为时间码"""
        total_seconds = frames / framerate
        hours = int(total_seconds // 3600)
        minutes = int((total_seconds % 3600) // 60)
        seconds = int(total_seconds % 60)
        return f"{hours:02d}:{minutes:02d}:{seconds:02d}"

    def timecode_to_seconds(self, timecode: str) -> float:
        """将时间码转换为秒数"""
        parts = timecode.split(':')
        if len(parts) == 3:
            hours, minutes, seconds = map(float, parts)
            return hours * 3600 + minutes * 60 + seconds
        elif len(parts) == 2:
            minutes, seconds = map(float, parts)
            return minutes * 60 + seconds
        return float(timecode)

    def get_video_files(self, directory: str) -> List[str]:
        """获取目录中的视频文件列表"""
        if not os.path.exists(directory):
            logger.error(f"目录不存在: {directory}")
            return []
            
        video_extensions = {'.mp4', '.MP4', '.avi', '.mov', '.mkv', '.flv'}
        
        try:
            all_files = os.listdir(directory)
            video_files = [
                f for f in all_files 
                if os.path.splitext(f)[1].lower() in video_extensions 
                and '999' not in f
            ]
            return natsort.natsorted(video_files)
        except Exception as e:
            logger.error(f"读取目录 {directory} 时出错: {e}")
            return []

    def read_and_process_frame(self, cap: cv2.VideoCapture, frame_num: int, roi: Tuple[int, int, int, int]) -> Optional[np.ndarray]:
        """读取并处理视频帧"""
        try:
            if not cap.set(cv2.CAP_PROP_POS_FRAMES, frame_num):
                logger.warning(f"无法跳转到帧 {frame_num}")
                return None
                
            ret, frame = cap.read()
            if not ret:
                logger.warning(f"无法读取帧 {frame_num}")
                return None
                
            # 验证ROI区域
            height, width = frame.shape[:2]
            x1, y1, x2, y2 = roi
            
            if (x1 >= width or x2 > width or y1 >= height or y2 > height or
                x1 < 0 or x2 <= x1 or y1 < 0 or y2 <= y1):
                logger.warning(f"ROI区域 {roi} 超出帧范围 {width}x{height}")
                return None
            
            # 提取ROI区域并转换为灰度
            roi_frame = frame[y1:y2, x1:x2]
            
            if len(roi_frame.shape) == 3:
                return cv2.cvtColor(roi_frame, cv2.COLOR_BGR2GRAY)
            return roi_frame
            
        except Exception as e:
            logger.error(f"处理帧 {frame_num} 时出错: {e}")
            return None

    def detect_key_frames(self, filepath: str, progress_callback: Optional[Callable] = None) -> Tuple[int, List[int]]:
        """检测视频中的关键帧"""
        if not SKIMAGE_AVAILABLE:
            logger.error("skimage库不可用，无法进行关键帧检测")
            return 0, []
            
        cap = cv2.VideoCapture(filepath)
        if not cap.isOpened():
            logger.error(f"无法打开视频文件: {filepath}")
            return 0, []
        
        try:
            total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
            fps = int(round(cap.get(cv2.CAP_PROP_FPS)))
            
            if fps <= 0:
                logger.error(f"无效的帧率: {fps}")
                return 0, []
                
            frame_interval = fps * self.config.granularity
            logger.info(f"视频信息: 总帧数={total_frames}, 帧率={fps}, 间隔帧数={frame_interval}")
            
            # 设置ROI区域
            roi = (self.config.frist_y, self.config.second_y, self.config.frist_x, self.config.second_x)
            key_frames = [self.config.frist_frame]
            current_frame = self.config.frist_frame
            
            while current_frame < total_frames - 30:
                if self.flag_stop:
                    break
                    
                ref_frame = self.read_and_process_frame(cap, current_frame, roi)
                if ref_frame is None:
                    break
                
                next_frame = min(current_frame + frame_interval, total_frames - 30)
                comp_frame = self.read_and_process_frame(cap, next_frame, roi)
                
                if comp_frame is None:
                    break
                
                # 计算相似度
                similarity = compare_ssim(ref_frame, comp_frame)
                
                if similarity < self.config.dissimilarity:
                    # 使用二分查找精确定位变化点
                    change_point = self.binary_search_change(
                        cap, current_frame, next_frame, self.config.dissimilarity, roi
                    )
                    key_frames.append(change_point)
                    current_frame = change_point
                else:
                    current_frame = next_frame
                
                # 更新进度
                if progress_callback:
                    progress = current_frame / total_frames * 50
                    progress_callback(f"分析: {os.path.basename(filepath)}  {progress*2:.2f}%", progress)

            # 确保最后一帧被包含
            if key_frames[-1] < total_frames - 30:
                key_frames.append(total_frames - 30)
                
            logger.info(f"检测完成，找到 {len(key_frames)} 个关键帧")
            return fps, key_frames
            
        finally:
            cap.release()

    def binary_search_change(self, cap: cv2.VideoCapture, start_frame: int, end_frame: int, 
                           threshold: float, roi: Tuple[int, int, int, int], min_interval: int = 15) -> int:
        """使用二分查找精确定位内容变化点"""
        low, high = start_frame, end_frame
        
        while high - low > min_interval:
            mid = (low + high) // 2
            
            low_frame = self.read_and_process_frame(cap, low, roi)
            mid_frame = self.read_and_process_frame(cap, mid, roi)
            
            if low_frame is None or mid_frame is None:
                return high
            
            similarity = compare_ssim(low_frame, mid_frame)
            
            if similarity < threshold:
                high = mid
            else:
                low = mid
                
        return high

    def process_time_points(self, frame_list: List[int], fps: int, min_interval: int = 60) -> List[str]:
        """处理时间点列表，合并间隔小的片段"""
        if not frame_list:
            return []
        
        time_list = [self.frames_to_timecode(fps, frame) for frame in frame_list]
        merged_list = [time_list[0]]
        
        for i in range(1, len(time_list)):
            current_seconds = self.timecode_to_seconds(time_list[i])
            prev_seconds = self.timecode_to_seconds(merged_list[-1])
            time_diff = current_seconds - prev_seconds
            
            if time_diff < min_interval:
                merged_list[-1] = time_list[i]
            else:
                merged_list.append(time_list[i])
        
        return merged_list

    def cut_video(self, video_path: str, timelist: List[str], progress_callback: Optional[Callable] = None) -> bool:
        """切割视频"""
        if not timelist or len(timelist) < 2:
            logger.warning("时间点列表无效，无法切割视频")
            return False
            
        success_count = 0
        
        for i in range(1, len(timelist)):
            if self.flag_stop:
                break
                
            starttime = timelist[i-1]
            endtime = timelist[i]
            
            # 生成输出文件名
            base_name = os.path.splitext(video_path)[0].replace(' ', '')
            output_path = f"{base_name}_{i}.mp4"
            
            try:
                cmd = [
                    'ffmpeg', '-ss', starttime, '-to', endtime,
                    '-i', video_path, '-vcodec', 'copy', '-acodec', 'copy',
                    '-y', output_path
                ]
                
                result = subprocess.run(
                    cmd, 
                    capture_output=True, 
                    text=True,
                    check=True
                )
                
                logger.info(f"成功分割: {starttime} -> {endtime}")
                success_count += 1
                
            except subprocess.CalledProcessError as e:
                logger.error(f"分割视频失败: {e.stderr if e.stderr else str(e)}")
            except FileNotFoundError:
                logger.error("ffmpeg未找到，请确保已安装ffmpeg并添加到PATH")
                break
            
            # 更新进度
            if progress_callback:
                progress = 50 + (i / len(timelist)) * 50
                progress_callback(f"分割: {os.path.basename(video_path)} {progress:.2f}%", progress)
        
        return success_count > 0

    def rename_videos(self) -> int:
        """重命名视频文件"""
        filepath = self.config.filepath
        file_list = self.get_video_files(filepath)
        renamed_count = 0
        
        for filename in file_list:
            if self.flag_stop:
                break
                
            # 提取日期和名称
            numbers = re.findall(r'\d+', os.path.basename(filename))
            name_part = ''.join(filter(str.isalpha, os.path.splitext(filename)[0]))
            
            if numbers and name_part:
                date_part = numbers[0] if len(numbers[0]) >= 6 else '202' + numbers[0]
                old_path = os.path.join(filepath, filename)
                
                if self.date_first:
                    new_name = f"{date_part}_{name_part}.mp4"
                else:
                    new_name = f"{name_part}_{date_part}.mp4"
                    
                new_path = os.path.join(filepath, new_name)
                
                if not os.path.exists(new_path):
                    try:
                        os.rename(old_path, new_path)
                        logger.info(f"重命名: {filename} -> {new_name}")
                        renamed_count += 1
                    except Exception as e:
                        logger.error(f"重命名失败: {e}")
        
        # 切换日期优先标志
        self.date_first = not self.date_first
        return renamed_count

    def group_files_by_pattern(self, directory: str, pattern: str, extension: str = '.mp4') -> Dict[str, List[str]]:
        """使用正则表达式模式分组文件"""
        files = [f for f in os.listdir(directory) if f.endswith(extension)]
        groups = defaultdict(list)
        
        try:
            regex = re.compile(pattern)
        except re.error as e:
            logger.error(f"正则表达式模式错误: {e}")
            return {}
        
        for filename in files:
            match = regex.match(filename)
            if match:
                key = match.group(1)
                groups[key].append(filename)
        
        # 对每个组的文件进行自然排序
        sorted_groups = {}
        for key, file_list in groups.items():
            if len(file_list) > 1:
                sorted_groups[key] = natsort.natsorted(file_list)
        
        return sorted_groups

    def merge_file_group(self, directory: str, group_name: str, file_list: List[str], 
                        output_filename: Optional[str] = None) -> bool:
        """合并一个文件组"""
        if output_filename is None:
            output_filename = f"{group_name}.mp4"
        
        full_output_path = os.path.join(directory, output_filename)
        full_file_paths = [os.path.join(directory, f) for f in file_list]
        
        # 创建文件列表文件
        list_filename = os.path.join(directory, f"filelist_{group_name}.txt")
        
        try:
            # 写入文件列表
            with open(list_filename, 'w', encoding='utf-8') as f:
                for file_path in full_file_paths:
                    escaped_path = file_path.replace("'", "'\\''")
                    f.write(f"file '{escaped_path}'\n")
            
            # 使用ffmpeg合并
            cmd = [
                'ffmpeg', '-y', '-f', 'concat', '-safe', '0',
                '-i', list_filename, '-c', 'copy', full_output_path
            ]
            
            logger.info(f"合并组 {group_name}: {file_list} -> {output_filename}")
            
            result = subprocess.run(
                cmd, 
                capture_output=True, 
                text=True,
                encoding='utf-8',
                errors='ignore',
                cwd=directory
            )
            
            if result.returncode == 0:
                logger.info(f"成功合并组 {group_name}")
                return True
            else:
                logger.error(f"合并组 {group_name} 失败: {result.stderr}")
                return False
                
        except Exception as e:
            logger.error(f"合并组 {group_name} 时出错: {str(e)}")
            return False
        finally:
            # 清理临时文件
            if os.path.exists(list_filename):
                try:
                    os.remove(list_filename)
                except:
                    pass

    def merge_all_groups(self, directory: str, pattern: str, delete_originals: bool = False, 
                        progress_callback: Optional[Callable] = None) -> Dict[str, int]:
        """合并所有匹配的文件组"""
        groups = self.group_files_by_pattern(directory, pattern)
        
        if not groups:
            logger.warning("未找到需要合并的文件组")
            return {"total": 0, "success": 0, "failed": 0}
        
        total_groups = len(groups)
        success_count = 0
        failed_count = 0
        
        logger.info(f"找到 {total_groups} 个需要合并的文件组")
        
        for i, (group_name, file_list) in enumerate(groups.items(), 1):
            if self.flag_stop:
                break
                
            # 更新进度
            if progress_callback:
                progress = (i / total_groups) * 100
                progress_callback(progress, f"正在合并组: {group_name}")
            
            # 合并文件组
            success = self.merge_file_group(directory, group_name, file_list)
            
            if success:
                success_count += 1
                if delete_originals:
                    self.delete_original_files(directory, file_list)
            else:
                failed_count += 1
        
        return {
            "total": total_groups,
            "success": success_count,
            "failed": failed_count
        }

    def delete_original_files(self, directory: str, file_list: List[str]) -> int:
        """删除原始文件"""
        deleted_count = 0
        
        for filename in file_list:
            try:
                file_path = os.path.join(directory, filename)
                if os.path.exists(file_path):
                    os.remove(file_path)
                    logger.info(f"已删除原始文件: {filename}")
                    deleted_count += 1
            except Exception as e:
                logger.error(f"删除文件 {filename} 失败: {str(e)}")
        
        return deleted_count

    def get_merge_status(self, directory: str) -> Dict[str, Any]:
        """获取目录中可合并的文件组信息"""
        patterns = [
            r'^(.+)[a-z]\.mp4$',
            r'^(.+)_[a-z]\.mp4$',
            r'^(.+)\d+\.mp4$',
            r'^(.+)_\d+\.mp4$',
        ]
        
        merge_info = {}
        
        for i, pattern in enumerate(patterns):
            groups = self.group_files_by_pattern(directory, pattern)
            if groups:
                merge_info[f"pattern_{i}"] = {
                    "pattern": pattern,
                    "groups": groups,
                    "count": len(groups)
                }
        
        return merge_info

    def merge_videos(self, merge_all: bool = False) -> bool:
        """合并视频文件"""
        try:
            directory = self.config.filepath
            
            if not os.path.exists(directory):
                logger.error(f"目录不存在: {directory}")
                return False
                
            merge_info = self.get_merge_status(directory)
            
            if not merge_info:
                logger.warning("未找到可合并的文件组")
                return False
            
            # 选择第一个模式进行合并
            first_pattern = next(iter(merge_info.values()))['pattern']
            
            def progress_callback(progress, message):
                logger.info(f"[{int(progress)}%] {message}")
            
            result = self.merge_all_groups(
                directory,
                pattern=first_pattern,
                delete_originals=False,
                progress_callback=progress_callback
            )
            
            logger.info(f"合并完成: 总共 {result['total']} 组, 成功 {result['success']} 组, 失败 {result['failed']} 组")
            return result['success'] > 0
                
        except Exception as e:
            logger.error(f"合并视频时出错: {e}")
            return False

    def process_all_videos(self, progress_callback: Optional[Callable] = None) -> int:
        """处理所有视频文件"""
        self.flag_stop = False
        filepath = self.config.filepath
        file_list = self.get_video_files(filepath)
        processed_count = 0
        
        if not file_list:
            logger.warning("没有找到视频文件")
            return 0
            
        for i, filename in enumerate(file_list):
            if self.flag_stop:
                break
                
            video_path = os.path.join(filepath, filename)
            self.current_file = filename
            
            if progress_callback:
                progress = i / len(file_list) * 50
                progress_callback(f"分析: {filename} {progress*2:.2f}%", progress)
                
            # 检测关键帧
            fps, frame_list = self.detect_key_frames(video_path, progress_callback)

            if not frame_list:
                continue
                
            # 处理时间点
            time_list = self.process_time_points(frame_list, fps, self.config.timespan)
            
            # 分割视频
            success = self.cut_video(video_path, time_list, progress_callback)
            
            if success:
                processed_count += 1
        
        if progress_callback:
            progress_callback("处理完成", 100)
            
        return processed_count

    def stop_processing(self):
        """停止处理"""
        self.flag_stop = True
        logger.info("处理已停止")

    # 由于篇幅限制，以下方法保持原有实现，但建议进行类似优化：
    # load_template_image, contains_template, mark_video_file, mark_videos_with_template, mark_videos, update_config_processing
    def load_template_image(self, image_path: str) -> Optional[np.ndarray]:
        """
        加载并处理模板图像
        
        参数:
            image_path: 图像文件路径
            
        返回:
            处理后的灰度图像或None（如果加载失败）
        """
        if not image_path or not os.path.exists(image_path):
            logger.error(f"模板图像路径无效或文件不存在: {image_path}")
            return None
        
        try:
            # 尝试多种图像加载方式
            img = cv2.imread(image_path, cv2.IMREAD_UNCHANGED)
            if img is None:
                # 尝试使用PIL作为备用方案
                try:
                    pil_img = Image.open(image_path)
                    img = np.array(pil_img)
                    if len(img.shape) == 3 and img.shape[2] == 4:  # RGBA转RGB
                        img = cv2.cvtColor(img, cv2.COLOR_RGBA2RGB)
                except Exception as pil_error:
                    logger.error(f"PIL也无法加载图像: {pil_error}")
                    return None
            
            # 处理不同通道的图像
            if len(img.shape) == 3:
                if img.shape[2] == 4:  # RGBA
                    img = cv2.cvtColor(img, cv2.COLOR_RGBA2GRAY)
                else:  # RGB或BGR
                    img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
            elif len(img.shape) == 2:  # 已经是灰度图
                pass
            else:
                logger.error(f"不支持的图像格式: {img.shape}")
                return None
            
            # 验证图像尺寸
            height, width = img.shape
            if height < 10 or width < 10:
                logger.warning(f"模板图像尺寸过小: {width}x{height}")
            
            logger.info(f"成功加载模板图像: {image_path}, 尺寸: {width}x{height}")
            return img
            
        except Exception as e:
            logger.error(f"加载模板图像时出错: {e}")
            return None

    def contains_template(self, video_path: str, template_img: np.ndarray, 
                        configs: Optional[Dict[str, Any]] = None) -> bool:
        """
        检查视频是否包含模板图像
        
        参数:
            video_path: 视频文件路径
            template_img: 模板图像
            configs: 配置参数字典
            
        返回:
            如果视频包含模板图像则返回True
        """
        if not SKIMAGE_AVAILABLE:
            logger.error("skimage库不可用，无法进行模板匹配")
            return False
        
        if template_img is None:
            logger.error("模板图像为空")
            return False
        
        if not os.path.exists(video_path):
            logger.error(f"视频文件不存在: {video_path}")
            return False
        
        # 使用默认配置或传入的配置
        config = configs or self.config.__dict__
        
        cap = cv2.VideoCapture(video_path)
        if not cap.isOpened():
            logger.error(f"无法打开视频: {video_path}")
            return False
        
        try:
            # 获取视频属性
            fps = int(round(cap.get(cv2.CAP_PROP_FPS)))
            frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
            
            if fps <= 0 or frame_count <= 0:
                logger.error(f"无效的视频属性: FPS={fps}, 帧数={frame_count}")
                return False
            
            # 设置ROI区域
            height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
            width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
            
            roi = (
                config.get('frist_x', 0),
                config.get('frist_y', 0),
                config.get('second_x', width),
                config.get('second_y', height)
            )
            
            # 验证ROI有效性
            x1, y1, x2, y2 = roi
            if not (0 <= x1 < x2 <= width and 0 <= y1 < y2 <= height):
                logger.warning(f"ROI区域无效: {roi}, 使用全帧")
                roi = (0, 0, width, height)
            
            # 计算采样帧
            sample_frames = self._calculate_sample_frames(frame_count, fps)
            if not sample_frames:
                logger.error("无法计算采样帧")
                return False
            
            # 检查采样帧
            similarity_threshold = config.get('similarity_threshold', 0.85)
            min_matches = config.get('min_matches', 2)
            match_count = 0
            
            logger.debug(f"检查视频: {os.path.basename(video_path)}, 采样帧: {len(sample_frames)}")
            
            for frame_num in sample_frames:
                if self.flag_stop:
                    break
                    
                # 读取并处理帧
                frame_img = self.read_and_process_frame(cap, frame_num, roi)
                if frame_img is None:
                    continue
                
                # 调整模板尺寸以匹配帧的ROI区域（如果需要）
                if frame_img.shape != template_img.shape:
                    try:
                        template_resized = cv2.resize(template_img, 
                                                    (frame_img.shape[1], frame_img.shape[0]))
                    except Exception as e:
                        logger.warning(f"调整模板尺寸失败: {e}")
                        continue
                else:
                    template_resized = template_img
                
                # 计算相似度
                try:
                    similarity = compare_ssim(template_resized, frame_img)
                    logger.debug(f"帧 {frame_num} 相似度: {similarity:.3f}")
                    
                    if similarity >= similarity_threshold:
                        match_count += 1
                        logger.info(f"匹配成功: 帧 {frame_num}, 相似度: {similarity:.3f}")
                        
                        # 如果已经达到最小匹配次数，提前退出
                        if match_count >= min_matches:
                            break
                            
                except Exception as e:
                    logger.warning(f"计算相似度失败: {e}")
                    continue
            
            logger.info(f"视频 {os.path.basename(video_path)} 匹配结果: {match_count}/{min_matches}")
            return match_count >= min_matches
            
        except Exception as e:
            logger.error(f"检查模板时出错: {e}")
            return False
        finally:
            cap.release()

    def mark_video_file(self, video_path: str) -> Optional[str]:
        """
        标记视频文件（重命名）
        
        参数:
            video_path: 视频文件路径
            
        返回:
            新文件路径或None（如果重命名失败）
        """
        if not os.path.exists(video_path):
            logger.error(f"视频文件不存在: {video_path}")
            return None
        
        try:
            directory = os.path.dirname(video_path)
            filename = os.path.basename(video_path)
            name, ext = os.path.splitext(filename)
            
            # 生成新文件名（避免重复）
            new_name = f"{name}_999{ext}"
            new_path = os.path.join(directory, new_name)
            
            # 检查是否已存在同名文件
            counter = 1
            while os.path.exists(new_path):
                new_name = f"{name}_999_{counter}{ext}"
                new_path = os.path.join(directory, new_name)
                counter += 1
            
            # 执行重命名
            os.rename(video_path, new_path)
            logger.info(f"标记视频: {filename} -> {os.path.basename(new_path)}")
            return new_path
            
        except PermissionError:
            logger.error(f"权限不足，无法重命名文件: {video_path}")
        except Exception as e:
            logger.error(f"重命名文件 {video_path} 时出错: {e}")
        
        return None

    def mark_videos_with_template(self, configs: Optional[Dict[str, Any]] = None, 
                                progress_callback: Optional[Callable] = None) -> List[str]:
        """
        使用模板图像标记视频文件
        
        参数:
            configs: 配置参数字典
            progress_callback: 进度回调函数
            
        返回:
            被标记的视频文件列表
        """
        if not SKIMAGE_AVAILABLE:
            logger.error("skimage库不可用，无法进行模板匹配")
            return []
        
        # 使用默认配置或传入的配置
        config = configs or self.config.__dict__
        directory = config.get('filepath', '')
        
        if not directory or not os.path.exists(directory):
            logger.error(f"目录不存在: {directory}")
            return []
        
        # 加载模板图像
        image_file = config.get('image_file')
        if not image_file:
            logger.error("未指定模板图像文件")
            return []
        
        template_img = self.load_template_image(image_file)
        if template_img is None:
            logger.error("无法加载模板图像")
            return []
        
        # 获取视频文件列表
        video_files = self.get_video_files(directory)
        if not video_files:
            logger.warning(f"在目录 {directory} 中未找到视频文件")
            return []
        
        marked_videos = []
        total_files = len(video_files)
        
        logger.info(f"开始标记视频，共 {total_files} 个文件")
        
        # 处理每个视频文件
        for i, video_file in enumerate(video_files):
            if self.flag_stop:
                logger.info("处理已停止")
                break
                
            video_path = os.path.join(directory, video_file)
            
            # 更新进度
            if progress_callback:
                progress = (i / total_files) * 100
                progress_callback(progress, f"分析: {video_file}")
            
            logger.info(f"分析: {video_file} ({i+1}/{total_files})")
            
            # 检查视频是否包含模板
            try:
                if self.contains_template(video_path, template_img, config):
                    # 重命名视频文件
                    new_path = self.mark_video_file(video_path)
                    if new_path:
                        marked_videos.append(new_path)
                        logger.info(f"✓ 标记视频: {video_file}")
                    else:
                        logger.error(f"✗ 标记失败: {video_file}")
                else:
                    logger.info(f"✗ 未匹配: {video_file}")
                    
            except Exception as e:
                logger.error(f"处理视频 {video_file} 时出错: {e}")
                continue
        
        # 最终进度
        if progress_callback:
            progress_callback(100, f"标记完成: {len(marked_videos)} 个文件被标记")
        
        logger.info(f"标记完成，共标记 {len(marked_videos)} 个视频文件")
        return marked_videos

    def mark_videos(self, progress_callback: Optional[Callable] = None) -> int:
        """
        标记视频文件的主入口方法
        
        参数:
            progress_callback: 进度回调函数
            
        返回:
            成功标记的视频数量
        """
        try:
            if progress_callback:
                progress_callback(0, "开始标记视频...")
            
            marked_videos = self.mark_videos_with_template(
                configs=self.config.__dict__,
                progress_callback=progress_callback
            )
            
            if progress_callback:
                progress_callback(100, f"标记完成: {len(marked_videos)} 个文件")
            
            return len(marked_videos)
            
        except Exception as e:
            logger.error(f"标记视频过程中出错: {e}")
            if progress_callback:
                progress_callback(0, f"标记出错: {str(e)}")
            return 0

    def _calculate_sample_frames(self, total_frames: int, fps: int) -> List[int]:
        """
        计算采样帧号（优化版本）
        
        参数:
            total_frames: 总帧数
            fps: 帧率
            
        返回:
            采样帧号列表
        """
        if total_frames <= 0 or fps <= 0:
            return []
        
        # 从第30帧开始采样（跳过可能的黑屏/片头）
        start_frame = min(30, total_frames - 1)
        
        # 根据视频长度动态调整采样间隔
        if total_frames <= 1800:  # 1分钟视频（30fps）
            interval = max(1, total_frames // 10)
        elif total_frames <= 9000:  # 5分钟视频
            interval = max(30, total_frames // 20)
        else:  # 长视频
            interval = max(60, total_frames // 30)
        
        # 生成采样帧列表
        sample_frames = list(range(start_frame, total_frames, interval))
        
        # 确保最后一帧被包含
        if sample_frames and sample_frames[-1] < total_frames - 1:
            sample_frames.append(total_frames - 1)
        
        # 限制最大采样数量（性能考虑）
        max_samples = 50
        if len(sample_frames) > max_samples:
            # 均匀抽取样本
            step = len(sample_frames) // max_samples
            sample_frames = sample_frames[::step][:max_samples]
        
        logger.debug(f"采样设置: 总帧数={total_frames}, 采样数={len(sample_frames)}, 间隔={interval}")
        return sample_frames
    # 注意：update_config_processing 方法需要大量重写，建议拆分为多个小方法
    def update_config_processing(self, video_path: Optional[str] = None):
        """更新配置处理 - 交互式ROI选择工具"""
        if not video_path or video_path == '':
            logger.warning("未选择视频文件")
            return
        
        try:
            # 打开视频文件
            cap = cv2.VideoCapture(video_path)
            if not cap.isOpened():
                logger.error("无法打开视频文件")
                return

            # 获取视频属性
            total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
            fps = cap.get(cv2.CAP_PROP_FPS)
            
            if fps <= 0 or total_frames <= 0:
                logger.error("无效的视频属性")
                cap.release()
                return

            duration = total_frames / fps
            logger.info(f"视频信息: {total_frames}帧, {fps:.1f}FPS, 时长: {duration:.1f}秒")

            # 计算抽取帧的数量和间隔
            count = min(self.config.count, 30)  # 限制最大采样数
            frame_interval = max(1, total_frames // count)
            
            # 存储采样帧
            selected_frames = []
            frame_positions = []
            
            # 抽取帧
            for i in range(count):
                frame_idx = min(i * frame_interval, total_frames - 1)
                cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
                ret, frame = cap.read()
                
                if not ret:
                    logger.warning(f"无法读取帧 {frame_idx}")
                    break
                    
                selected_frames.append(frame.copy())
                frame_positions.append(frame_idx)

            cap.release()

            # 如果没有成功读取到帧，直接返回
            if not selected_frames:
                logger.error("未能读取到任何视频帧")
                return

            # 初始化ROI选择状态
            self._setup_roi_selection(selected_frames, frame_positions, fps)

        except Exception as e:
            logger.error(f"配置处理过程中出错: {e}")

    def _setup_roi_selection(self, frames: List[np.ndarray], frame_positions: List[int], fps: float):
        """设置ROI选择界面"""
        # 初始化状态变量
        self.current_frame_index = 0
        self.selected_roi = None
        self.roi_coordinates = None
        self.drawing = False
        self.ix, self.iy = -1, -1
        
        # 存储最后保存的ROI坐标
        self.last_saved_coords = (
            self.config.frist_x,
            self.config.frist_y,
            self.config.second_x,
            self.config.second_y
        )
        
        # 创建窗口
        cv2.namedWindow('ROI Selection Tool', cv2.WINDOW_NORMAL)
        #cv2.resizeWindow('ROI Selection Tool', 800, 600)
        cv2.setMouseCallback('ROI Selection Tool', self._mouse_callback)
        
        # 存储帧数据
        self.frames = frames
        self.frame_positions = frame_positions
        self.fps = fps
        
        # 显示第一帧
        self._display_current_frame()
        
        # 主循环
        self._roi_selection_loop()

    def _mouse_callback(self, event, x, y, flags, param):
        """鼠标回调函数"""
        if event == cv2.EVENT_LBUTTONDOWN:
            self.drawing = True
            self.ix, self.iy = x, y
            self.selected_roi = None
            
        elif event == cv2.EVENT_MOUSEMOVE:
            if self.drawing:
                img_copy = self.frames[self.current_frame_index].copy()
                self._draw_ui_elements(img_copy)
                cv2.rectangle(img_copy, (self.ix, self.iy), (x, y), (0, 255, 0), 1)
                cv2.imshow('ROI Selection Tool', img_copy)
        
        elif event == cv2.EVENT_LBUTTONUP:
            self.drawing = False
            x1, y1 = min(self.ix, x), min(self.iy, y)
            x2, y2 = max(self.ix, x), max(self.iy, y)
            
            # 确保ROI有效
            height, width = self.frames[self.current_frame_index].shape[:2]
            x1 = max(0, min(x1, width - 1))
            y1 = max(0, min(y1, height - 1))
            x2 = max(1, min(x2, width))
            y2 = max(1, min(y2, height))
            
            self.selected_roi = (x1, y1, x2 - x1, y2 - y1)
            self.roi_coordinates = [
                (x1, y1), (x2, y1), (x1, y2), (x2, y2)
            ]
            
            logger.info(f"选择了ROI: ({x1}, {y1}) - ({x2}, {y2})")
            self._display_current_frame()

    def _display_current_frame(self):
        """显示当前帧，保持原始尺寸和比例"""
        """显示当前帧，保持原始尺寸和比例，并添加UI信息"""
        if self.current_frame_index >= len(self.frames):
            return
            
        frame = self.frames[self.current_frame_index].copy()
        frame_idx = self.frame_positions[self.current_frame_index]
        timestamp = frame_idx / self.fps
        
        # 获取原始帧尺寸
        original_height, original_width = frame.shape[:2]
        
        # 在帧上显示UI信息
        frame = self._draw_frame_info(frame, frame_idx, timestamp)
        
        # 设置窗口属性以保持原始尺寸
        window_name = 'ROI Selection Tool'
        
        # 获取屏幕尺寸
        '''try:
            window_rect = cv2.getWindowImageRect(window_name)
            screen_width = window_rect[2] if window_rect[2] > 0 else 1920
            screen_height = window_rect[3] if window_rect[3] > 0 else 1080
        except:
            screen_width, screen_height = 1920, 1080
        
        # 计算缩放比例以适应屏幕
        scale_x = screen_width / original_width
        scale_y = screen_height / original_height
        scale = min(scale_x, scale_y, 1.0)  # 最大保持原始尺寸，不超过屏幕
        
        if scale < 1.0:
            # 需要缩放
            new_width = int(original_width * scale)
            new_height = int(original_height * scale)
            display_frame = cv2.resize(frame, (new_width, new_height), interpolation=cv2.INTER_AREA)
        else:
            # 保持原始尺寸
            display_frame = frame
            new_width = original_width
            new_height = original_height'''
        
        # 设置窗口大小
        cv2.namedWindow(window_name, cv2.WINDOW_NORMAL)
        cv2.resizeWindow(window_name, original_width, original_height)
        
        # 显示帧
        cv2.imshow(window_name, frame)
        
        # 在窗口标题中显示尺寸信息
        #cv2.setWindowTitle(window_name, 
                        #f"ROI Tool - Original: {original_width}x{original_height}")

    def _draw_frame_info(self, frame: np.ndarray, frame_idx: int, timestamp: float) -> np.ndarray:
        """在帧上绘制UI信息"""
        # 转换BGR到RGB用于PIL处理
        if len(frame.shape) == 3:
            frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        else:
            frame_rgb = cv2.cvtColor(frame, cv2.COLOR_GRAY2RGB)
            
        pil_img = Image.fromarray(frame_rgb)
        draw = ImageDraw.Draw(pil_img)
        
        # 显示帧信息
        frame_info = f"帧号: {frame_idx} 时间: {timestamp:.2f}s"
        
        # 显示当前坐标状态
        coord_status = "有保存的坐标" if hasattr(self, 'last_saved_coords') and self.last_saved_coords else "无保存的坐标"
        coord_text = f"坐标状态: {coord_status}"
        
        # 显示操作指南
        guide1 = f"{self.current_frame_index + 1}/{len(self.frames)} - c:保存配置, b:保存画面"
        guide2 = "n:下一帧, p:上一帧, x:退出"
        
        # 文本列表
        texts = [frame_info, coord_text, guide1, guide2]
        
        # 使用合适的字体
        font = self.font_medium or ImageFont.load_default()
        
        # 绘制文本背景（提高可读性）
        for i, text in enumerate(texts):
            try:
                bbox = draw.textbbox((0, 0), text, font=font)
                text_width = bbox[2] - bbox[0]
                text_height = bbox[3] - bbox[1]
                
                # 绘制半透明背景
                bg_x1, bg_y1 = 10, 10 + i * (text_height + 8)
                bg_x2, bg_y2 = 10 + text_width + 15, 10 + (i + 1) * (text_height + 8)
                
                # 创建半透明背景
                bg_layer = Image.new('RGBA', pil_img.size, (0, 0, 0, 0))
                bg_draw = ImageDraw.Draw(bg_layer)
                bg_draw.rectangle([bg_x1, bg_y1, bg_x2, bg_y2], fill=(0, 0, 0, 128))
                
                # 合并背景层
                pil_img = Image.alpha_composite(pil_img.convert('RGBA'), bg_layer).convert('RGB')
                draw = ImageDraw.Draw(pil_img)
                
                # 绘制文本
                draw.text((15, 10 + i * (text_height + 8)), text, font=font, fill=(255, 255, 255))
                
            except Exception as e:
                # 如果PIL绘制失败，使用OpenCV备用方案
                cv2.putText(frame, text, (15, 30 + i * 30), 
                        cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 255), 2)
        
        # 转换回OpenCV格式
        try:
            frame = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
        except:
            pass  # 保持原帧
        
        # 绘制保存的ROI（蓝色）
        if hasattr(self, 'last_saved_coords') and self.last_saved_coords:
            x1, y1, x2, y2 = self.last_saved_coords
            cv2.rectangle(frame, (x1, y1), (x2, y2), (255, 0, 0), 2)
            
            # 在ROI旁边显示尺寸信息
            roi_width = x2 - x1
            roi_height = y2 - y1
            size_text = f"{roi_width}x{roi_height}"
            cv2.putText(frame, size_text, (x1, y1 - 10), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
        
        # 绘制当前选择的ROI（绿色）
        if self.selected_roi:
            x1, y1, w, h = self.selected_roi
            x2, y2 = x1 + w, y1 + h
            cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
            
            # 显示尺寸信息
            size_text = f"{w}x{h}"
            cv2.putText(frame, size_text, (x1, y1 - 10), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
            
            # 绘制角点
            for corner in [(x1, y1), (x2, y1), (x1, y2), (x2, y2)]:
                cv2.circle(frame, corner, 4, (0, 0, 255), -1)
        
        return frame


    

    def _display_frame_with_ratio(self, frame: np.ndarray):
        """显示帧并保持原始比例"""
        if frame is None or frame.size == 0:
            return
        
        original_height, original_width = frame.shape[:2]
        
        # 获取当前窗口尺寸
        try:
            window_rect = cv2.getWindowImageRect('ROI Selection Tool')
            if window_rect[2] > 0 and window_rect[3] > 0:
                screen_width, screen_height = window_rect[2], window_rect[3]
            else:
                screen_width, screen_height = 800, 600
        except:
            screen_width, screen_height = 800, 600
        
        # 计算保持比例的缩放
        scale_x = screen_width / original_width
        scale_y = screen_height / original_height
        self.scale_factor = min(scale_x, scale_y, 1.0)  # 最大保持原始尺寸
        
        if self.scale_factor < 1.0:
            # 需要缩放
            new_width = int(original_width * self.scale_factor)
            new_height = int(original_height * self.scale_factor)
            display_frame = cv2.resize(frame, (new_width, new_height), interpolation=cv2.INTER_AREA)
        else:
            # 保持原始尺寸
            display_frame = frame
            new_width = original_width
            new_height = original_height
        
        # 设置窗口大小
        cv2.resizeWindow('ROI Selection Tool', new_width, new_height)
        
        # 显示帧
        cv2.imshow('ROI Selection Tool', display_frame)

    def _draw_ui_elements(self, frame: np.ndarray, frame_idx: int = None, timestamp: float = None):
        """绘制UI元素到帧上，使用原始坐标"""
        if frame is None:
            return
            
        # 转换BGR到RGB用于PIL处理
        if len(frame.shape) == 3:
            frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
        else:
            frame_rgb = cv2.cvtColor(frame, cv2.COLOR_GRAY2RGB)
            
        pil_img = Image.fromarray(frame_rgb)
        draw = ImageDraw.Draw(pil_img)
        
        # 绘制文本信息
        if frame_idx is not None and timestamp is not None:
            info_texts = [
                f"帧号: {frame_idx}",
                f"时间: {timestamp:.2f}s",
                f"进度: {self.current_frame_index + 1}/{len(self.frames)}",
                "操作指南:",
                "c - 保存配置",
                "b - 保存画面", 
                "n/p - 切换帧",
                "x - 退出"
            ]
            
            # 使用合适的字体大小
            font_size = max(14, min(24, frame.shape[0] // 30))
            try:
                if self.font_small:
                    font = ImageFont.truetype(self.font_small.path, font_size)
                else:
                    font = ImageFont.load_default()
            except:
                font = ImageFont.load_default()
            
            # 绘制文本背景（提高可读性）
            for i, text in enumerate(info_texts):
                bbox = draw.textbbox((0, 0), text, font=font)
                text_width = bbox[2] - bbox[0]
                text_height = bbox[3] - bbox[1]
                
                # 绘制半透明背景
                bg_color = (0, 0, 0, 128)  # 黑色半透明
                draw.rectangle([10, 10 + i * (text_height + 5), 
                            10 + text_width + 10, 10 + (i + 1) * (text_height + 5)], 
                            fill=bg_color)
                
                # 绘制文本
                draw.text((15, 10 + i * (text_height + 5)), text, font=font, fill=(255, 255, 255))
        
        # 绘制保存的ROI（蓝色）
        if hasattr(self, 'last_saved_coords') and self.last_saved_coords:
            x1, y1, x2, y2 = self.last_saved_coords
            cv2.rectangle(frame, (x1, y1), (x2, y2), (255, 0, 0), 2)
            
            # 在ROI旁边显示尺寸信息
            roi_width = x2 - x1
            roi_height = y2 - y1
            size_text = f"{roi_width}x{roi_height}"
            cv2.putText(frame, size_text, (x1, y1 - 10), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
        
        # 绘制当前选择的ROI（绿色）
        if self.selected_roi:
            x1, y1, w, h = self.selected_roi
            x2, y2 = x1 + w, y1 + h
            cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
            
            # 显示尺寸信息
            size_text = f"{w}x{h}"
            cv2.putText(frame, size_text, (x1, y1 - 10), 
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 1)
            
            # 绘制角点
            for corner in [(x1, y1), (x2, y1), (x1, y2), (x2, y2)]:
                cv2.circle(frame, corner, 4, (0, 0, 255), -1)

    def _roi_selection_loop(self):
        """ROI选择主循环"""
        print("\n" + "="*50)
        print("ROI选择工具已启动")
        print("操作指南:")
        print("• 鼠标拖拽选择区域")
        print("• c - 保存当前ROI配置")
        print("• b - 使用保存的坐标保存当前帧画面") 
        print("• n - 下一帧")
        print("• p - 上一帧")
        print("• x/ESC - 退出程序")
        print("="*50)
        
        while True:
            key = cv2.waitKey(1) & 0xFF
            
            # 保存配置
            if key == ord('c'):
                if self._save_roi_config():
                    print("✓ ROI配置已保存")
                else:
                    print("✗ 保存配置失败")
            
            # 保存画面
            elif key == ord('b'):
                if self._save_frame_with_roi():
                    print("✓ 画面已保存")
                else:
                    print("✗ 保存画面失败")
            
            # 下一帧
            elif key == ord('n'):
                self.current_frame_index = (self.current_frame_index + 1) % len(self.frames)
                self._display_current_frame()
                print(f"切换到下一帧 ({self.current_frame_index + 1}/{len(self.frames)})")
            
            # 上一帧
            elif key == ord('p'):
                self.current_frame_index = (self.current_frame_index - 1) % len(self.frames)
                self._display_current_frame()
                print(f"切换到上一帧 ({self.current_frame_index + 1}/{len(self.frames)})")
            
            # 退出
            elif key == ord('x') or key == 27 or cv2.getWindowProperty('ROI Selection Tool', cv2.WND_PROP_VISIBLE) < 1:
                print("退出ROI选择工具")
                break
        
        cv2.destroyAllWindows()

    def _save_roi_config(self):
        """保存ROI配置"""
        if not self.selected_roi:
            logger.warning("没有选择ROI，无法保存配置")
            return False
        
        try:
            x1, y1, w, h = self.selected_roi
            x2, y2 = x1 + w, y1 + h
            
            # 更新配置
            self.config.frist_x = x1
            self.config.frist_y = y1
            self.config.second_x = x2
            self.config.second_y = y2
            
            # 保存到文件
            if self.config.save_to_file():
                self.last_saved_coords = (x1, y1, x2, y2)
                logger.info("ROI配置已保存")
                return True
            
        except Exception as e:
            logger.error(f"保存配置失败: {e}")
        
        return False

    def _save_frame_with_roi(self):
        """使用ROI保存帧画面"""
        if not hasattr(self, 'last_saved_coords') or not self.last_saved_coords:
            logger.warning("没有保存的ROI配置")
            return False
        
        try:
            x1, y1, x2, y2 = self.last_saved_coords
            current_frame = self.frames[self.current_frame_index]
            
            # 提取ROI区域
            roi_image = current_frame[y1:y2, x1:x2]
            
            # 生成文件名
            if self.config.image_file:
                roi_filename = self.config.image_file
            else:
                timestamp = time.strftime("%Y%m%d_%H%M%S")
                roi_filename = f"roi_frame_{timestamp}.jpg"
            
            # 保存图像
            success = cv2.imwrite(roi_filename, roi_image)
            if success:
                logger.info(f"ROI图像已保存: {roi_filename}")
                return True
            else:
                logger.error("保存图像失败")
                
        except Exception as e:
            logger.error(f"保存帧画面失败: {e}")
        
        return False


class VideoProcessorApp:
    def __init__(self, root):
        self.root = root
        self.processor = VideoProcessor()
        self.setup_ui()
        
    def setup_ui(self):
        """设置用户界面"""
        self.root.title("视频处理工具")
        self.root.geometry("800x600")
        self.root.minsize(600, 400)
        
        # 主框架
        main_frame = tk.Frame(self.root)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        # 状态显示
        status_frame = tk.Frame(main_frame)
        status_frame.pack(fill=tk.X, pady=5)
        
        self.status_var = tk.StringVar(value="就绪")
        status_label = tk.Label(status_frame, textvariable=self.status_var, font=("Arial", 12))
        status_label.pack(side=tk.LEFT)
        
        # 进度条
        progress_frame = tk.Frame(main_frame)
        progress_frame.pack(fill=tk.X, pady=5)
        
        self.progress_var = tk.DoubleVar()
        progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, maximum=100)
        progress_bar.pack(fill=tk.X)
        
        # 按钮框架
        button_frame = tk.Frame(main_frame)
        button_frame.pack(fill=tk.X, pady=10)
        
        # 按钮配置
        buttons = [
            ("分析并分割", self.analyze_and_cut),
            ("检查复盘", self.check_review),
            ("重命名", self.rename_videos),
            ("合并视频", self.merge_videos),
            ("更新坐标", self.update_processing),
            ("停止", self.stop_processing),
            ("退出", self.safe_quit)
        ]
        
        # 创建按钮
        for text, command in buttons:
            btn = tk.Button(button_frame, text=text, command=command, width=12)
            btn.pack(side=tk.LEFT, padx=5)
        
        # 日志显示
        log_frame = tk.LabelFrame(main_frame, text="操作日志")
        log_frame.pack(fill=tk.BOTH, expand=True, pady=5)
        
        self.log_text = tk.Text(log_frame, height=15)
        self.log_text.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 滚动条
        scrollbar = tk.Scrollbar(self.log_text)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        self.log_text.config(yscrollcommand=scrollbar.set)
        scrollbar.config(command=self.log_text.yview)
        
        # 重定向日志到文本框
        self.setup_log_redirection()
        
        # 绑定窗口关闭事件
        self.root.protocol("WM_DELETE_WINDOW", self.safe_quit)
    
    def setup_log_redirection(self):
        """设置日志重定向到文本框"""
        class TextHandler(logging.Handler):
            def __init__(self, text_widget):
                super().__init__()
                self.text_widget = text_widget
                
            def emit(self, record):
                msg = self.format(record)
                def append():
                    self.text_widget.insert(tk.END, msg + '\n')
                    self.text_widget.see(tk.END)
                self.text_widget.after(0, append)
        
        text_handler = TextHandler(self.log_text)
        text_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
        logger.addHandler(text_handler)
    
    def update_status(self, message: str, progress: float = 0):
        """更新状态和进度"""
        def update():
            self.status_var.set(message)
            self.progress_var.set(progress)
        
        self.root.after(0, update)
    
    def check_review(self):
        """标记视频文件"""
        def process():
            try:
                self.update_status("正在标记视频...", 0)
                self.processor.mark_videos(self.update_status)
                self.update_status("标记完成", 100)
            except Exception as e:
                logger.error(f"标记过程中出错: {e}")
                self.update_status("标记出错", 0)
        
        threading.Thread(target=process, daemon=True).start()
    
    def analyze_and_cut(self):
        """分析并分割视频"""
        def process():
            try:
                self.update_status("开始处理视频...", 0)
                count = self.processor.process_all_videos(self.update_status)
                self.update_status(f"处理完成，成功处理 {count} 个视频", 100)
            except Exception as e:
                logger.error(f"处理过程中出错: {e}")
                self.update_status("处理出错", 0)
        
        threading.Thread(target=process, daemon=True).start()
    
    def rename_videos(self):
        """重命名视频"""
        def process():
            try:
                self.update_status("正在重命名视频...", 0)
                count = self.processor.rename_videos()
                self.update_status(f"重命名完成，重命名 {count} 个文件", 100)
            except Exception as e:
                logger.error(f"重命名过程中出错: {e}")
                self.update_status("重命名出错", 0)
        
        threading.Thread(target=process, daemon=True).start()
    
    def merge_videos(self):
        """合并视频"""
        def process():
            try:
                self.update_status("正在合并视频...", 0)
                success = self.processor.merge_videos()
                status = "合并完成" if success else "合并失败"
                self.update_status(status, 100)
            except Exception as e:
                logger.error(f"合并过程中出错: {e}")
                self.update_status("合并出错", 0)
        
        threading.Thread(target=process, daemon=True).start()
    
    def update_processing(self):
        """更新配置处理"""
        video_path = filedialog.askopenfilename(
            title="选择视频文件",
            filetypes=[("视频文件", "*.mp4;*.avi;*.mov;*.mkv"), ("所有文件", "*.*")]
        )
        
        if video_path:
            threading.Thread(
                target=self.processor.update_config_processing,
                args=(video_path,),
                daemon=True
            ).start()
    
    def stop_processing(self):
        """停止处理"""
        self.processor.stop_processing()
        self.update_status("已停止", 0)
    
    def safe_quit(self):
        """安全退出程序"""
        self.processor.stop_processing()
        self.root.quit()
        self.root.destroy()


def main():
    """主函数"""
    # 检查必要依赖
    if not SKIMAGE_AVAILABLE:
        print("警告: 缺少必要的依赖库，请安装: pip install scikit-image pillow")
    
    root = tk.Tk()
    app = VideoProcessorApp(root)
    
    # 居中显示窗口
    root.update_idletasks()
    width = root.winfo_width()
    height = root.winfo_height()
    x = (root.winfo_screenwidth() // 2) - (width // 2)
    y = (root.winfo_screenheight() // 2) - (height // 2)
    root.geometry(f"{width}x{height}+{x}+{y}")
    
    root.mainloop()


if __name__ == "__main__":
    main()